#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>

// 使用一个字节表示6个led char 0:led1 1:led2 2:led3 ---- 5:led6
// 0表示关闭 1表示开启
// 0000_0001：led0开启，其余所有LED关闭

#define CNAME "myleds"

struct cdev *cdev;
struct class *cls;
struct device *dev;

int major = 0;
int minor = 0;
char *gpioname[] = {"led1", "led2", "led3", "led4", "led5", "led6"};
unsigned int gpionum[6] = {0};
char ledstatus[8] = {0};

int myleds_open(struct inode *inode, struct file *file)
{
    printk("%s,%s,%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t myleds_read(struct file *file, char __user *user, size_t size, loff_t *loffs)
{
    int ret;
    printk("%s,%s,%d\n", __FILE__, __func__, __LINE__);
    if (size > 8)
        size = 8;
    ret = copy_to_user(user, ledstatus, size);
    printk("ret = %d\n", ret);
    if(ret)
    {
        printk(KERN_ERR "copy_to_user error!\n");
        return -EIO;
    }
    return size;
}
ssize_t myleds_write(struct file *file, const char __user *user, size_t size, loff_t *loffs)
{
    int ret, i;
    printk("%s,%s,%d\n", __FILE__, __func__, __LINE__);

    if (size > 8)
        size = 8;
    ret = copy_from_user(ledstatus, user, size);
    if(ret)
    {
        printk(KERN_ERR "copy_from_user %d error!\n", ret);
        return -EIO;
    }

    for ( i = 0; i < 6; i++)
    {
        if(ledstatus[i] == '1')
            gpio_direction_output(gpionum[i], 1);
        else
            gpio_direction_output(gpionum[i], 0);
    }

    return size;
}
int myleds_close(struct inode *inode, struct file *file)
{
    printk("%s,%s,%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

const struct file_operations fops = {
    .open = myleds_open,
    .read = myleds_read,
    .write = myleds_write,
    .release = myleds_close,
};

int myleds_probe(struct platform_device *pdev)
{
    int ret, i;
    int devno;

    printk("%s,%s,%d\n", __FILE__, __func__, __LINE__);
    cdev = cdev_alloc();
    if (cdev == NULL)
    {
        printk(KERN_ERR "cdev_alloc error!\n");
        ret = -ENOMEM;
        goto err1;
    }

    cdev_init(cdev, &fops);

    ret = alloc_chrdev_region(&devno, 1, 0, CNAME);
    if (ret)
    {
        printk(KERN_ERR "alloc_chrdev_region error!\n");
        goto err2;
    }

    major = MAJOR(devno);
    minor = MINOR(devno);

    ret = cdev_add(cdev, MKDEV(major, minor), 1);
    if (ret)
    {
        printk(KERN_ERR "cdev_add error!\n");
        goto err3;
    }

    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls))
    {
        printk(KERN_ERR "class_create error!\n");
        ret = PTR_ERR(cls);
        goto err4;
    }

    dev = device_create(cls, NULL, MKDEV(major, minor), NULL, CNAME);
    if (IS_ERR(dev))
    {
        printk(KERN_ERR "device_create error!\n");
        ret = PTR_ERR(dev);
        goto err5;
    }

    for (i = 0; i < 6; i++)
    {
        gpionum[i] = of_get_named_gpio(pdev->dev.of_node, gpioname[i], 0);
        if (gpionum[i] <= 0)
        {
            printk(KERN_ERR "of_get_named_gpio %d error!\n", i);
            ret = gpionum[i];
            goto err6;
        }

        ret = gpio_request(gpionum[i], NULL);
        if (ret)
        {
            printk(KERN_ERR "gpio_request %d error!\n", i);
            goto err6;
        }

        gpio_direction_output(gpionum[i], 0);
    }

    return 0;

err6:
    for (--i; i >= 0; i--)
    {
        gpio_free(gpionum[i]);
    }

err5:
    class_destroy(cls);

err4:
    cdev_del(cdev);

err3:
    unregister_chrdev_region(MKDEV(major, minor), 1);

err2:
    kfree(cdev);

err1:
    return ret;
}
int myleds_remove(struct platform_device *pdev)
{
    int i;
    printk("%s,%s,%d\n", __FILE__, __func__, __LINE__);

    for (i = 0; i < 6; i++)
    {
        gpio_direction_output(gpionum[i], 0);
        gpio_free(gpionum[i]);
    }
    device_destroy(cls, MKDEV(major, minor));
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major, minor), 1);
    kfree(cdev);

    return 0;
}

struct of_device_id oftable[] = {
    {.compatible = "hqyj,leds",},
    {},
};

struct platform_driver myleds = {
    .probe = myleds_probe,
    .remove = myleds_remove,
    .driver = {
        .name = "myleds",
        .of_match_table = oftable,
    },
};

module_platform_driver(myleds);
MODULE_LICENSE("GPL");
